package cs236369.webserver;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

import javax.xml.xpath.XPathExpressionException;

import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpDateGenerator;

import Logging.Monitor;

public class WebServer {

	private final Parser parsedConfig;
	private ServerSocket serverSocket = null;
	
	private ThreadPool socketPool = null;
	private ThreadPool requestPool = null ;
	
	public WebServer(InputStream config) throws Exception {
		
		parsedConfig = new Parser(config);
		serverSocket = new ServerSocket();
	}
	
	/**
	 * This method initializes the objects that participate in the server.
	 * It initializes the thread pools and specific-type handlers.
	 * @throws IOException
	 */
	public void init() throws IOException {
		
		try {
			// initializing the server socket to read on a specific port
			int port = parsedConfig.getPort();
			serverSocket = new ServerSocket(port);
			Monitor.write(this, "Creating a server that listens on port:"+port);
			
			// initializing the thread pools  
			int nSocketReaders = parsedConfig.getNumSocketReaders();
			int sQueueSize = parsedConfig.getNumSocketQueue();
			socketPool = new ThreadPool(nSocketReaders, sQueueSize);
			Monitor.write(this, "Initialized the socket thread pool with:"
								+nSocketReaders+
								" threads and with this size of queue:"+sQueueSize);
			
			int nRequestReaders = parsedConfig.getNumRequestReaders();
			int rQueueSize = parsedConfig.getNumRequestQueue();
			requestPool = new ThreadPool(nRequestReaders, rQueueSize);
			Monitor.write(this, "Initialized the socket thread pool with:"
					+nRequestReaders+
					" threads and with this size of queue:"+rQueueSize);
			
			//TODO implement the type handlers
		} catch (XPathExpressionException e) {
			// TODO handle
		}
		
	}
	
	/**
	 * Fires off the server: listens endlessly for client connections.
	 */
	public void start() {
		
		// starting the threads in the thread pool 
		socketPool.start();
		requestPool.start();
		
		// starting to listen for connections
		Socket clientSocket = null;
		try {
			
			while(true) {
				clientSocket= serverSocket.accept();
				Monitor.write(this, "Recieved a connection from the client");
				socketPool.addTask(
							new SocketReaderHandler(clientSocket,requestPool));
			}
			
		} catch (IOException e) {
			// TODO handle
		} catch (Exception fullQueueMessage) {// the queue of sockets is full and cann't receive connections 
			// sending the err message to the client
			String errMessage = fullQueueMessage.getMessage(); 
			sendMessageToClient(clientSocket,errMessage);
		}
	} 
	
	public static void main(String[] args) throws Exception {
		WebServer srv = new WebServer(new FileInputStream("config.xml"));
		srv.init();
		srv.start();
	}
	
	/**
	 * The message header we send includes the following headers:
	 * <ol>
	 * 	<li>The time of the response generation</li>
	 * 	<li>The content length of the response</li>
	 * 	<li>The mime type of the response content</li>
	 * 	<li>A statement that the server closes the connection upon termination</li>
	 * </ol>
	 * @param clientSocket
	 * @param message
	 */
	private void sendMessageToClient(Socket clientSocket, String message) {
		
		String errMessage = "<html>" +
								"<head>" +
									"<title>Service Unavailable</title>" +
								"</head>" +
								"<body>" +
									message+
								"</body>" +
							"</html>";
									
		DefaultHttpServerConnection conn=null;
		try {
			// initialize socket
			HttpParams params = new BasicHttpParams(); 
			conn = new DefaultHttpServerConnection();
			conn.bind(clientSocket, params);
			
			// adding the relevant headers
			HttpResponse response = 
					 new BasicHttpResponse(HttpVersion.HTTP_1_1, 503, "Service Unavailable");
			response.addHeader("Date", new HttpDateGenerator().getCurrentDate());
			response.addHeader("Content-Type", "text/html");
			response.addHeader("Connection","close");
			
			// sending the message
			Monitor.write(this,"Sending the following error:"+errMessage);
			response.setEntity(new StringEntity(errMessage));
			conn.sendResponseHeader(response);
			conn.sendResponseEntity(response);
			
		} catch (IOException exit) {
			// TODO check if finally occurs after a throw if not add the shutdowns
			throw new RuntimeException("A fatal error has occured while " +
										"attempeting to send a message to the client.");
		} catch (HttpException ignore) {} 
		finally {
			poolShutDown();
			socketShutDown(clientSocket, conn);
		} 
	} 
	
	/**
	 * Closing the sockets passed as parameters. If an error occurs while attempting
	 * to close a runtime exception will be thrown.
	 */
	private void socketShutDown(	Socket clientSocket,
									DefaultHttpServerConnection conn) {
		
		Monitor.write(this, "Closing the clientSocket");
		try {
			if( conn != null) {
				conn.close();
				if(clientSocket != null) {
					clientSocket.close();
				}
			}
		} catch (IOException e) {
			throw new RuntimeException("Unable to properly close the client socket.\n"+
					e.getMessage());
		}
	}
	
	/**
	 * Deactivating the threads in our thread pools 
	 */
	private void poolShutDown() {
		
		Monitor.write(this, "Closing the thread pools");
		socketPool.shoutdown();
		requestPool.shoutdown();
	}
}
